#include "StdAfx.h"
#include "CCullThread.h"
#include "ObjMan.h"
#include "CCullRenderer.h"
#include <IJobManager_JobDelegator.h>

//	#define NO_THREAD_FOR_PIX

DECLARE_JOB("CheckOcclusion", TOcclusionCheckJob, NAsyncCull::CCullThread::CheckOcclusion );
JobManager::SJobState g_CheckOcclusionJobState;




#if EMBED_PHYSICS_AS_FIBER
#if !defined(__SPU__)
  namespace 
  {
     static const uint64_t s_Freq = sys_time_get_timebase_frequency();
  }
# endif 
#	define	YIELD_PHYSFIBER() { JobManager::Fiber::SwitchFiberDirect(); }
#	define YIELD_LOOP_LIMIT	200
#else







#   define	YIELD_PHYSFIBER() { Sleep(1); }
#		define	YIELD_LOOP_LIMIT	1000

#endif

	namespace
	{
		static uint s_arrSRenderMeshRenderSize = 0;
	}







	enum{CULL_SIZEX=256};
	enum{CULL_SIZEY=128};


typedef NAsyncCull::CCullRenderer<CULL_SIZEX,CULL_SIZEY>	tdCullRasterizer;

















	static						NAsyncCull::tdVertexCache g_VertexCache;
	static uint8				g_RasterizerBuffer[sizeof(tdCullRasterizer) + 127];
	tdCullRasterizer*	g_Rasterizer;
	#define RASTERIZER (*g_Rasterizer)


namespace NAsyncCull
{

#ifndef __SPU__
CCullThread::CCullThread():
m_CullState(ECS_WAITING),
m_Enabled(false)
{
#if !defined(PS3)
	{
		intptr_t ptr = intptr_t(g_RasterizerBuffer) + 127;
		g_Rasterizer = new(reinterpret_cast<void*>(ptr & ~127)) tdCullRasterizer();
	}



#endif
	for(int i = 0; i < MAX_CAMS; i++)
	{
		m_Cameras[i].SetIdentity();
	}
	m_CameraID = -1;

	m_parrSRenderMeshRenderList = 0;
	m_parrSRenderMeshRenderModifiedList = 0;
	m_NearPlane = 0;
	m_FarPlane = 0;
	m_NearestMax = 0;




}

CCullThread::~CCullThread()
{
	READ_WRITE_BARRIER



}


void CCullThread::CalcDist(const Vec3& rViewDir,SRenderMeshRenderInfo& rRMRI)const
{
#if !defined(NO_MESH_RASTERIZATION)
	rRMRI.Dist	=	(rViewDir*(rRMRI.aabb.min+rRMRI.aabb.max))*0.5f;
	IRenderMesh* pMesh	=	rRMRI.pRenderMeshLowLod?rRMRI.pRenderMeshLowLod:rRMRI.pRenderMesh;
	if(pMesh)
	{
		rRMRI.VertexCount	= pMesh->GetVerticesCount();
		rRMRI.IndexCount	=	pMesh->GetIndicesCount();
		rRMRI.VertexSize	=	0;
		rRMRI.pVertices		=	pMesh->GetPosPtr(rRMRI.VertexSize,FSL_READ);
		rRMRI.pIndices		=	pMesh->GetIndexPtr(FSL_READ);
	}
#endif
}


template<class T>
bool RMRICompare(const T& rElement0,const T& rElement1)
{
	return rElement0.Dist<rElement1.Dist;
}

void CCullThread::PrepareCullbufferAsync(const CCamera& rCam)
{
	Matrix44									MatProj;
	Matrix44									MatView;
	Matrix44									MatViewProj;

	CCamera tmp_cam = m_pRenderer->GetCamera();
	m_pRenderer->SetCamera(rCam);
	m_pRenderer->GetModelViewMatrix(reinterpret_cast<f32*>(&MatView));
	m_pRenderer->GetProjectionMatrix(reinterpret_cast<f32*>(&MatProj));
	m_pRenderer->SetCamera(tmp_cam);
	m_ViewDir	=	rCam.GetViewdir();
	MatViewProj.Multiply(MatView,MatProj);

	MatViewProj.Transpose();

	const float SCALEX		=	static_cast<float>(CULL_SIZEX/2);
	const float SCALEY		=	static_cast<float>(CULL_SIZEY/2);
	const Matrix44A MatScreen(	SCALEX,0.f,0.f,SCALEX,
															0.f,-SCALEY,0.f,SCALEY,
															0.f,0.f,1.f,0.f,
															0.f,0.f,0.f,1.f);
	m_MatScreenViewProj	=	MatScreen*MatViewProj;
	m_MatScreenViewProjTransposed = GetTransposed44(m_MatScreenViewProj);
	m_NearPlane =  rCam.GetNearPlane();
	m_FarPlane =  rCam.GetFarPlane();
	m_NearestMax = m_pRenderer->GetNearestRangeMax();





}

void CCullThread::CullStart(const CCamera& rCam,
											 PodArray<SRenderMeshRenderInfo>&	arrSRenderMeshRenderList,
											 PodArray<SRenderMeshRenderInfoModified>&	arrSRenderMeshRenderModifiedList)
{
	FUNCTION_PROFILER_3DENGINE;

	s_arrSRenderMeshRenderSize = arrSRenderMeshRenderList.size();

	m_parrSRenderMeshRenderList	=	&arrSRenderMeshRenderList;
	m_parrSRenderMeshRenderModifiedList	=	&arrSRenderMeshRenderModifiedList;

	m_Position	=	rCam.GetPosition();

	Matrix44									MatProj;
	Matrix44									MatView;
	Matrix44									MatViewProj;

	CCamera tmp_cam = m_pRenderer->GetCamera();
	m_pRenderer->SetCamera(rCam);
	m_pRenderer->GetModelViewMatrix(reinterpret_cast<f32*>(&MatView));
	m_pRenderer->GetProjectionMatrix(reinterpret_cast<f32*>(&MatProj));
	m_pRenderer->SetCamera(tmp_cam);
	m_ViewDir	=	rCam.GetViewdir();
	MatViewProj.Multiply(MatView,MatProj);

	m_CameraID = ++m_CameraID % MAX_CAMS; 
	m_Cameras[m_CameraID] = MatViewProj;

	MatViewProj.Transpose();

	const float SCALEX		=	static_cast<float>(CULL_SIZEX/2);
	const float SCALEY		=	static_cast<float>(CULL_SIZEY/2);
	const Matrix44A MatScreen(	SCALEX,0.f,0.f,SCALEX,
															0.f,-SCALEY,0.f,SCALEY,
															0.f,0.f,1.f,0.f,
															0.f,0.f,0.f,1.f);

	m_MatScreenViewProj	=	MatScreen*MatViewProj;
	m_MatScreenViewProjTransposed = GetTransposed44(m_MatScreenViewProj);
	m_NearPlane =  rCam.GetNearPlane();
	m_FarPlane =  rCam.GetFarPlane();
	m_NearestMax = m_pRenderer->GetNearestRangeMax();

#ifndef _RELEASE
	static ICVar *pDebug = gEnv->pConsole->GetCVar("e_CoverageBufferDebug");
	RASTERIZER.m_DebugRender = pDebug->GetIVal() > 0? 1:0;
	RASTERIZER.DrawDebug(m_pRenderer,1);
#endif
	{
		// only run the culling in a thread on PS3 to offload the work to SPU, for other platforms it doesn't make a 
		// performance difference as along as we don't have some work to put in the waiting place
#ifndef NO_THREAD_FOR_PIX
		m_CullState	=	ECS_CHECKOCCLUSION|ECS_PREPAREDEPTHBUFFER;
		uint32 task = m_CullState;



		Prepare();

		RunJob( task, &g_CheckOcclusionJobState );
#if EMBED_PHYSICS_AS_FIBER && defined(PS3) && !defined(__SPU__)
    JobManager::Fiber::SwitchFiberDirect();
#endif 
#else
		// Just for debugging and using PIX timing capture
		Prepare();
		// CheckOcclusion called in CullWait
#endif
	}
}

void CCullThread::CullWait(const CCamera& rCam,
											 PodArray<SRenderMeshRenderInfo>&	arrSRenderMeshRenderList,
											 PodArray<SRenderMeshRenderInfoModified>&	arrSRenderMeshRenderModifiedList)
{
	// CullWait: Implements a wait for cull task: on PS3 this should have been running on SPU on 360 it is a thread
	
	if (s_arrSRenderMeshRenderSize != arrSRenderMeshRenderList.size())
	{
		printf( "ERROR <CCullThread> Lists out of sync! %d != %d\n", (int)s_arrSRenderMeshRenderSize, (int)(arrSRenderMeshRenderList.size()) );
		CryDebugBreak();
	}

#ifndef NO_THREAD_FOR_PIX
	{
#if EMBED_PHYSICS_AS_FIBER && !defined(__SPU__)
		if (g_CheckOcclusionJobState.IsRunning())
		{
    	JobManager::Fiber::SwitchFiberDirect();
		}
#endif		

		gEnv->GetJobManager()->WaitForJob(g_CheckOcclusionJobState);
	}
#else
	// CheckOcclusion immediately - NOW
	CheckOcclusion(ECS_PREPAREDEPTHBUFFER|ECS_CHECKOCCLUSION);
#endif

	m_CullState=ECS_WAITING;

#ifndef _RELEASE
	static ICVar *pDebug = gEnv->pConsole->GetCVar("e_CoverageBufferDebug");
	RASTERIZER.m_DebugRender = pDebug->GetIVal() > 0? 1:0;
	RASTERIZER.DrawDebug(m_pRenderer,1);
#endif
}

void CCullThread::Prepare()
{




#if !defined(NO_MESH_RASTERIZATION)
	PodArray<SRenderMeshRenderInfo>&	arrSRenderMeshRenderList	=	*m_parrSRenderMeshRenderList;
	PodArray<SRenderMeshRenderInfoModified>&	arrSRenderMeshRenderModifiedList	=	*m_parrSRenderMeshRenderModifiedList;

	InitDistance(m_ViewDir,arrSRenderMeshRenderList);
	InitDistance(m_ViewDir,arrSRenderMeshRenderModifiedList);
		
	std::sort(arrSRenderMeshRenderList.begin(),arrSRenderMeshRenderList.end(),RMRICompare<SRenderMeshRenderInfo>);
	std::sort(arrSRenderMeshRenderModifiedList.begin(),arrSRenderMeshRenderModifiedList.end(),RMRICompare<SRenderMeshRenderInfoModified>);
#endif
}

void CCullThread::RunJob(uint32 Task, JobManager::SJobState* pJobState)
{
	memset( pJobState, 0, sizeof(*pJobState) );
	TOcclusionCheckJob job( Task );
	job.SetClassInstance( this );
	job.RegisterJobState( pJobState );
	job.SetCacheMode(JobManager::SPUBackend::eCM_4 );
	job.SetPriorityLevel(JobManager::eHighPriority);
	job.Run();
}
#endif // ifndef __SPU__

namespace
{








































	ILINE SRenderMeshRenderInfo& RenderMeshInfo(SRenderMeshRenderInfo* pInfo)
	{
		return *pInfo;
	}
	ILINE void PutRenderMeshInfo(){}
	ILINE void SyncRenderMeshInfo(){}

}

SPU_ENTRY(CheckOcclusion)
void CCullThread::CheckOcclusion(uint32 Task)
{
#if defined(__SPU__) || !defined(_RELEASE) || !defined(PS3)
	FUNCTION_PROFILER_3DENGINE;
	using namespace NVMath;
/*
#ifdef __SPU__
	Vec3 locPosition(m_Position.x, m_Position.y, m_Position.z);
	#define m_Position locPosition
	Matrix44A RESTRICT_REFERENCE rMatFinal;
	Matrix44A RESTRICT_REFERENCE rMatFinalT;
	Matrix44A	matScreenViewProj _ALIGN(16);
	Matrix44A	matScreenViewProjTransposed _ALIGN(16);
	memtransfer_from_main((void*)&matScreenViewProj, (void*)&m_MatScreenViewProj, sizeof(Matrix44A),3);
	memtransfer_from_main((void*)&matScreenViewProjTransposed, (void*)&m_MatScreenViewProjTransposed, sizeof(Matrix44A),3);
#else*/
	uint8 AlignBuffer[2*sizeof(Matrix44A)+16];
	size_t pBuffer	=	(reinterpret_cast<size_t>(AlignBuffer)+15)&~15;
	Matrix44A& RESTRICT_REFERENCE rMatFinal		=	reinterpret_cast<Matrix44A*>(pBuffer)[0];
	Matrix44A& RESTRICT_REFERENCE rMatFinalT	=	reinterpret_cast<Matrix44A*>(pBuffer)[1];
//#endif

#if defined(PS3) || defined(XENON) || defined(WIN32) || defined(gringo)
	bool bHWZBuffer	=	true;
#else
	bool bHWZBuffer	=	false;
#endif

#if !defined(_RELEASE) && !defined(__SPU__)
	static ICVar *pReproj = gEnv->pConsole->GetCVar("e_CoverageBufferReproj");
	bHWZBuffer	=	pReproj->GetIVal()>0;
#endif

//#ifdef __SPU__
//	memtransfer_sync(3);
//	#define m_MatScreenViewProj matScreenViewProj
//	#define m_MatScreenViewProjTransposed matScreenViewProjTransposed
//#endif







	if(Task&ECS_PREPAREDEPTHBUFFER)
	{
		if(bHWZBuffer)
		{
			float* pTmpBuffer=0;



			uint Tmp[80];
			Matrix44A& rTmp	=	*SPU_LOCAL_PTR(reinterpret_cast<Matrix44A*>((reinterpret_cast<size_t>(Tmp)+15)&~15));
			rTmp	=	m_MatScreenViewProjTransposed;
			m_Enabled	=	RASTERIZER.DownLoadHWDepthBuffer(rTmp,m_NearPlane,m_FarPlane,m_NearestMax, GetCVars()->e_CoverageBufferBias,pTmpBuffer);
		}
		else
		{
			RASTERIZER.Clear();
		}



	}
	if(!(Task&ECS_CHECKOCCLUSION))
		return;

	if(!m_Enabled)
		return;










	SRenderMeshRenderInfo *const __restrict parrSRenderMeshRenderList = SPU_MAIN_PTR(m_parrSRenderMeshRenderList->GetElements());
	SRenderMeshRenderInfoModified *const __restrict parrSRenderMeshRenderModifiedList = SPU_MAIN_PTR(m_parrSRenderMeshRenderModifiedList->GetElements());

	const size_t	S0	=	m_parrSRenderMeshRenderList->size();
	const size_t	S1	=	m_parrSRenderMeshRenderModifiedList->size();

	const AABB PosAABB	=	AABB(m_Position,0.5f);
	const float Bias	=	GetCVars()->e_CoverageBufferAABBExpand;
	rMatFinalT	=	GetTransposed44(m_MatScreenViewProj);
	int Yes=0;
	int No=0;
	for(size_t a=0,S=S0+S1;a<S;a++)
	{
		//walk both list in order in respect to distance
#ifdef NO_MESH_RASTERIZATION
		SRenderMeshRenderInfo& RESTRICT_REFERENCE rRMRI	=	SPU_LOCAL_REF(RenderMeshInfo(a<S0?&parrSRenderMeshRenderList[a]:&parrSRenderMeshRenderModifiedList[a-S0]));
#else
		SRenderMeshRenderInfo& RESTRICT_REFERENCE rRMRI	=	SPU_LOCAL_REF(RenderMeshInfo(Idx1>=S1?&parrSRenderMeshRenderList[Idx0++]:
																		Idx0>=S0?&parrSRenderMeshRenderModifiedList[Idx1++]:
																		parrSRenderMeshRenderList[Idx0].Dist<parrSRenderMeshRenderModifiedList[Idx1].Dist?
																				&parrSRenderMeshRenderList[Idx0++]:&parrSRenderMeshRenderModifiedList[Idx1++]));
#endif
		AABB	rAABB		=	rRMRI.aabb;
		if(Bias<0.f)
			rAABB.Expand((rAABB.max-rAABB.min)*Bias-Vec3(Bias,Bias,Bias));
		else
			rAABB.Expand(Vec3(Bias*rRMRI.fEntDistance));
		const bool ForceVisible	=	rAABB.IsIntersectBox(PosAABB);
		if(!ForceVisible)
		{
			if(!RASTERIZER.TestAABB(reinterpret_cast<NVMath::vec4*>(&rMatFinalT),rAABB.min,rAABB.max,m_Position))
			{
				rRMRI.pEnt = NULL;
				PutRenderMeshInfo();
				No++;
				continue;
			}
			else
				Yes++;
		}
		else
			Yes++;
#if !defined(NO_MESH_RASTERIZATION)
//		else
		if(!rRMRI.ZWrite)
			continue;

		Matrix34A MatWorld	=	SPU_MAIN_PTR(rRMRI.pRenderObject)->GetMatrix();







//		IRenderMesh* pMesh	=	rRMRI.pRenderMeshLowLod?rRMRI.pRenderMeshLowLod:rRMRI.pRenderMesh;
		IRenderMesh* pMesh	=	rRMRI.pRenderMesh;
		if(!pMesh)
			continue;

		const uint32 VertexCount	= pMesh->GetVerticesCount();
		const	uint32 IndexCount		=	pMesh->GetIndicesCount();
		int32 VertexSize					=	0;
		const uint8* __restrict  pVertices		=	pMesh->GetPosPtr(VertexSize,FSL_READ);
		const uint16* __restrict  pIndices		=	pMesh->GetIndexPtr(FSL_READ);
		IF(!pVertices || !pIndices || !IndexCount || !VertexCount, 0)
			continue;


		bool Rasterized = true;//if a mesh does not fit into the local store, treat as visible






















		{

			//Polygonal test
			rMatFinal	=	(m_MatScreenViewProj*MatWorld);
			rMatFinal	=	GetTransposed44(rMatFinal);
			//Rasterized = rRMRI.ZWrite?
				RASTERIZER.Rasterize<true> (	reinterpret_cast<NVMath::vec4*>(&rMatFinal), g_VertexCache, 
																			pIndices,IndexCount,pVertices,VertexSize,VertexCount);
				//RASTERIZER.Rasterize<false>(	reinterpret_cast<NVMath::vec4*>(&rMatFinal), g_VertexCache, 
				//															pIndices,IndexCount,pVertices,VertexSize,VertexCount);
		}
		//if(!ForceVisible && !Rasterized)
		//{
		//	rRMRI.pRenderObject	=	NULL;
		//	PutRenderMeshInfo();
		//}
#endif
	}
	SyncRenderMeshInfo();
	#undef m_Position
	#undef m_MatScreenViewProj
	#undef m_MatScreenViewProjTransposed
#endif
}

}
